This notebooks explores the results from running cell type annotation with SingleR using the NBAtlas. The NBAtlas reference was aggregated with SingelR prior to model training.

In this notebook, we visualize inferred cell type annotations directly, compare them to normal consensus cell types, and validate cell type assignments with marker genes.

Setup

options(readr.show_col_types = FALSE)
suppressPackageStartupMessages({
  library(ggplot2)
  library(patchwork)
  library(SingleCellExperiment)
})

theme_set(theme_bw())

# Define color ramp for shared use in the heatmaps
heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))
# Set heatmap padding option
ComplexHeatmap::ht_opt(TITLE_PADDING = grid::unit(0.6, "in"))

Paths

# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)

module_dir <- file.path(repository_base, "analyses", "cell-type-neuroblastoma-04")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results", "singler")
data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
# SingleR files
singler_files <- list.files(
  path = results_dir,
  pattern = "_singler-annotations\\.tsv",
  recursive = TRUE,
  full.names = TRUE
) |>
  # add names as library id
  purrr::set_names(
    \(x) {
      stringr::str_remove(basename(x), "_singler-annotations.tsv")
    }
  )

# merged SCE file
# OBTAINED FROM PORTAL DIRECTLY on 2025-07-09
sce_file <- file.path(
  data_dir,
  "SCPCP000004_merged.rds"
)

# broad consensus cell type groups
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"

# palette files
recoded_palette_file <- file.path(
  module_dir,
  "palettes",
  "harmonized-cell-type-palette.tsv"
)

nbatlas_palette_file <- file.path(
  module_dir,
  "palettes",
  "nbatlas-marker-genes-palette.tsv"
)

# marker genes for validation
consensus_markers_file <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/validation-markers.tsv"
nbatlas_markers_file <- file.path(ref_dir, "nbatlas-marker-genes.tsv")

Functions

# Source Jaccard and heatmap utilities functions
source(file.path(module_dir, "scripts", "utils", "jaccard-utils.R"))

# Source additional utilities functions:
# - harmonize_celltypes()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))

Prepare input data

Read SCE object to get consensus cell types and UMAP coordinates:

merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL

# RUH ROH!!!
# https://github.com/AlexsLemonade/scpcaTools/issues/298
merged_sce$cell_id <- colnames(merged_sce)

Read cell type data frames:

singler_df <- singler_files |>
  purrr::map(readr::read_tsv) |>
  purrr::list_rbind(names_to = "library_id") |>
  dplyr::select(-delta.next, -labels) |>
  dplyr::mutate(
    # add cell id column for unique rows & joining
    cell_id = glue::glue("{library_id}-{barcodes}"),
    # recode NA -> "unknown_singler"
    pruned.labels = ifelse(
      is.na(pruned.labels),
      "unknown_singler",
      pruned.labels
    )
  )

# validation data frames
validation_df <- readr::read_tsv(validation_url) |>
  dplyr::select(consensus_annotation, validation_group_annotation)
consensus_markers_df <- readr::read_tsv(consensus_markers_file)
nbatlas_markers_df <- readr::read_tsv(nbatlas_markers_file)


# set up palettes

# recoded to shared colors
recoded_palette_df <- readr::read_tsv(recoded_palette_file)
recoded_celltype_pal <- recoded_palette_df$hex_color
names(recoded_celltype_pal) <- recoded_palette_df$harmonized_label

# only the NBAtlas labels - use for validation dotplot
nbatlas_palette_df <- readr::read_tsv(nbatlas_palette_file)
nbatlas_celltype_pal <- nbatlas_palette_df$hex_color
names(nbatlas_celltype_pal) <- nbatlas_palette_df$NBAtlas_label

Join and prepare data for use:

celltype_df <- scuttle::makePerCellDF(
  merged_sce,
  use.coldata = c("barcodes", "sample_id", "library_id", "consensus_celltype_annotation"),
  use.dimred = c("UMAP")
) |>
  # there are repeated barcodes so we need to keep cell_id around
  tibble::rownames_to_column("cell_id") |>
  dplyr::rename(
    UMAP1 = UMAP.1,
    UMAP2 = UMAP.2,
    consensus_annotation = consensus_celltype_annotation
  ) |>
  dplyr::left_join(validation_df, by = "consensus_annotation") |>
  # recode NAs to "unknown_consensus" and remove full consensus labels
  dplyr::mutate(validation_group_annotation = ifelse(
    is.na(validation_group_annotation),
    "unknown_consensus",
    validation_group_annotation
  )) |>
  dplyr::select(-consensus_annotation) |>
  dplyr::left_join(singler_df, by = c("cell_id", "barcodes", "library_id"))

# Recode NBAtlas cell types where possible so they match with colors
celltype_recoded_df <- celltype_df |>
  # rename to make annotation sources more clear
  dplyr::rename(
    "consensus_validation" = validation_group_annotation,
    "singler" = pruned.labels
  ) |>
  # pivot longer for wrangling
  tidyr::pivot_longer(
    c(consensus_validation, singler),
    names_to = "annotation_type",
    values_to = "label"
  ) |> 
  harmonize_celltypes(label, label_recoded) |>
  dplyr::select(-label)

Cell types in NBAtlas

Throughout this notebook, we compare the cell type labels from SingleR with the consensus cell type labels. To facilitate this, the resulting SingleR labels from NBAtlas were grouped together to both a) mirror (where possible) the broad consensus labels, and b) to make some visualizations more manageable.

The table below shows the NBAtlas cell types and how they are represented in visualizations, unless otherwise stated.

Broad label shown in figures Associated NBAtlas labels
T cell CD4+ T cell, CD8+ T cell, Treg, NKT cell
dendritic cell cDC1, cDC2/DC3, Migratory cDC
natural killer cell cell Circulating NK cell, TOX2+/KIT+ NK cell, Resident NK cell
monocyte CD4+ T cell, Classical monocyte, Patrolling monocyte
myeloid Neutrophil
Neuroendocrine Neuroendocrine
fibroblast Fibroblast
macrophage Macrophage
B cell B cell
plasma cell Plasma
endothelial Endothelial
Stromal other (NBAtlas) Stromal other
Schwann Schwann
RBCs RBCs
Immune cycling Immune cycling
pDC pDC

Cells labeled singler_unknown are those for which SingleR could not reliably assign an annotation.

Heatmap

This section compares SingleR annotations to consensus cell type annotations using a heatmap. The heatmap is colored by Jaccard similarity index.

# Pivot wider for heatmap functions
celltype_recoded_wide_df <- celltype_recoded_df |>
  tidyr::pivot_wider(
    names_from = annotation_type,
    values_from = label_recoded
  )

heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))

make_jaccard_heatmap(
  celltype_recoded_wide_df,
  "consensus_validation",
  "singler",
  "Consensus validation label",
  "SingleR NBAtlas label"
)

We broadly see high compatibility between SingleR and consensus labels. In addition, SingleR mostly classifies the unknown consensus cells as Neuroendocrine, which is consistent with our expectation that these are mostly tumor cells.

UMAPs

This section visualizes and compares SingleR annotations to consensus cell type annotations using UMAPs. Note that the displayed UMAP is from a merged object that has not been batch-corrected.

Cell type labels have been harmonized between sources wherever possible. Note that each set of labels has its own “stromal” category which the labels distinguish. In addition, cells labeled unknown_consensus are those with no assigned consensus label, and cells labeled unknown_singler are those where SingleR could not confidently assign a label.

Complete UMAP

First, we display the consensus and SingleR annotations for all cells.

ggplot(celltype_recoded_df) +
  aes(x = UMAP1, y = UMAP2, color = label_recoded) +
  geom_point(size = 0.25, alpha = 0.5) +
  scale_color_manual(
    values = recoded_celltype_pal,
    name = "Harmonized cell types"
  ) +
  facet_wrap(vars(annotation_type)) +
  coord_equal() +
  theme(
    legend.position = "bottom",
    axis.ticks = element_blank(),
    axis.text = element_blank()
  ) +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1)))

SingleR annotations only

Below we display a faceted UMAP to highlight the SingleR annotations. Light gray cells in each panel represent all other cell types.

celltype_recoded_df |>
  dplyr::filter(annotation_type == "singler") |>
  faceted_umap(
    annotation_column = label_recoded,
    celltype_colors = recoded_celltype_pal
  )

Faceted comparison to consensus cell types

Below, we display faceted UMAPs highlighting a single cell type but considering only cell types that have direct correspondence between SingleR and consensus cell types. This allows us to see if the normal cells that SingleR is labeling correspond well to the normal cells identified by consensus labels. Each row displays a cell type where the left column shows the consensus version and the right column shows the SingleR version.

In addition, we include a category below putative-tumor to directly compare the unknown consensus labels with the Neuroendocrine cells labeled by SingleR. While these categories are not necessarily directly comparable, they are each most likely to contain tumor cells.

Also, note that the myeloid-labeled SingleR cells are only neutrophils, but the consensus myeloid grouping contains additional cell types.

direct_celltype_matches <- c(
  "B cell",
  "T cell",
  "myeloid",
  "macrophage",
  "monocyte",
  "dendritic cell",
  "natural killer cell",
  "fibroblast",
  "endothelial cell",
  "plasma cell",
  "natural killer cell",
  # we'll use this label to be able to directly compare unknown_consensus to Neuroendocrine
  "putative-tumor"
)


celltype_facet_df <- celltype_recoded_df |>
  dplyr::mutate(
    # recode so Neuroendocrine matches with unknown_consensus in the plot
    label_recoded = ifelse(
      label_recoded %in% c("Neuroendocrine", "unknown_consensus"),
      "putative-tumor",
      label_recoded
    )) |>
  dplyr::filter(label_recoded %in% direct_celltype_matches)

faceted_umap(
  celltype_facet_df,
  annotation_column = label_recoded,
  celltype_colors = recoded_celltype_pal,
  facet_type = "grid",
  annotation_type_column = annotation_type
)

There appears to be a reasonable correspondence between consensus- and SingleR-colored UMAPS for each cell type. We see that SingleR classified more cells compared to consensus, which we expected, and we also see that the additional cells that SingleR labeled are roughly in the same neighborhood as their corresponding consensus labels.

Marker gene expression dotplots

In this section we’ll validate annotations using two sets of marker genes:

# Prepare the expressed_genes vector
# we only care about if that gene is expressed otherwise we won't waste memory and include it
gene_sums <- rowData(merged_sce) |>
  as.data.frame() |>
  dplyr::select(contains("detected")) |>
  as.matrix() |>
  rowSums()
expressed_genes <- names(gene_sums)[gene_sums > 0]

NBAtlas marker genes

This dotplot shows the top-5 highest log2FC marker genes per NBAtlas cell type for validation. Marker genes are taken directly from the NBAtlas paper where they were identified with Seurat::FindMarkers().

This plot does not show the recoded SingleR labels but rather all labels that have associated marker genes; therefore, there are more cell type categories in this plot than in other plots in this report.

# number of marker genes to consider per validation group
n_marker_genes <- 5

top_nbatlas_markers_df <- nbatlas_markers_df |>
  # keep only the top `n_marker_genes`
  dplyr::filter(direction == "up") |>
  dplyr::group_by(NBAtlas_label) |>
  dplyr::mutate(rank_lfc = rank(-avg_log2FC)) |>
  dplyr::ungroup() |>
  dplyr::filter(rank_lfc <= n_marker_genes) |>
  dplyr::select(marker_gene_label = NBAtlas_label, gene_symbol, ensembl_gene_id)
# we want to plot the literal labels here
full_celltype_df <- celltype_df |>
  dplyr::select(
    cell_id, 
    # the dotplot function expects this column name
    label_recoded = pruned.labels
  ) |>
  dplyr::mutate(
    label_recoded = dplyr::case_when(
      # collapse the dendritic cells & monocytes to match what's in the marker genes
      stringr::str_detect(label_recoded, "cDC") ~ "cDC", 
      stringr::str_detect(label_recoded, "monocyte") ~ "Monocyte",
      label_recoded == "NE" ~ "Neuroendocrine",
      .default = label_recoded
  )) |>
  dplyr::filter(label_recoded != "unknown_singler")


# get total number of cells per final annotation group and set up y_label
total_cells_df <- full_celltype_df |>
  dplyr::count(label_recoded, name = "total_cells") |>
  dplyr::arrange(desc(total_cells)) |>
  dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})"))

singler_order <- total_cells_df$y_label
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = singler_order)
nbatlas_bar_order <- total_cells_df$label_recoded
generate_dotplot(
  merged_sce = merged_sce,
  markers_df = top_nbatlas_markers_df,
  singler_df = full_celltype_df,
  total_cells_df = total_cells_df,
  expressed_genes = expressed_genes,
  bar_order = nbatlas_bar_order, 
  celltype_palette = nbatlas_celltype_pal, 
  min_cells = 0
)
`summarise()` has grouped output by 'label_recoded', 'marker_gene_label'. You can override using the `.groups` argument.

For validation, we expect to see high marker gene expression along the diagonal of the dotplot where cell type labels match with their marker genes. We do broadly see high expression along the diagonal, which means our labeled cells have some amount of similar signal to those in NBAtlas. Some sets of marker genes show high expression across multiple cell type categories in particular for closely related cell types (e.g. monocyte, macrophage, myeloid, dendritic, or T-cells and NK-cells), but there do not appear to be any strongly unexpected gene expression patterns.

Consensus validation marker genes

This dotplot shows expression of consensus validation marker genes across matching cell types labeled with SingleR.

# Prepare data frame with singler labels to plot
singler_recoded_df <- celltype_recoded_wide_df |>
  # we don't consider the NAs here
  dplyr::filter(singler != "unknown_singler") |>
  dplyr::select(cell_id, label_recoded = singler)


# get total number of cells per final annotation group and set up y_label
total_cells_df <- singler_recoded_df |>
  dplyr::count(label_recoded, name = "total_cells") |>
  dplyr::arrange(desc(total_cells)) |>
  dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})"))

singler_order <- total_cells_df$y_label
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = singler_order)
# prepare markers for dotplot
dotplot_consensus_markers_df <- consensus_markers_df |>
  # consider only matching labels
  dplyr::filter(validation_group_annotation %in% singler_recoded_df$label_recoded) |>
  dplyr::select(
    marker_gene_label = validation_group_annotation,
    ensembl_gene_id,
    gene_symbol
  )

# get the bar order
consensus_bar_order <- total_cells_df |>
  dplyr::select(label_recoded, y_label) |>
  # inner!!!!
  dplyr::inner_join(
    dotplot_consensus_markers_df, 
    by = c("label_recoded" = "marker_gene_label")
  ) |>
  dplyr::arrange(y_label) |>
  dplyr::pull(label_recoded) |>
  unique()
generate_dotplot(
  merged_sce = merged_sce,
  markers_df = dotplot_consensus_markers_df,
  singler_df = singler_recoded_df,
  total_cells_df = total_cells_df,
  expressed_genes = expressed_genes,
  bar_order = consensus_bar_order, 
  celltype_palette = recoded_celltype_pal, 
  min_cells = 0
)
`summarise()` has grouped output by 'label_recoded', 'marker_gene_label'. You can override using the `.groups` argument.

Again, marker gene expression tends to be high in the matching cell type categories, with the possible exception of B cell which has somewhat lower gene expression of fewer genes; but, there is still some expected expression there. We also see some “promiscuity” where marker genes for closely-related cell types show high expression.

Compare expression when SingleR and consensus annotations match

Next, we look at expression of consensus validation marker genes for each normal/harmonized cell type label. We compare expression across groups of cells:

Each panel contains only cells that SingleR annotated with the given label, and shows only marker genes associated with that given cell type. We expect that cells whose consensus and SingleR annotations agree may show higher gene expression for the given marker.

# this time we are not considering putative tumor
direct_celltype_matches <- c(
  "B cell",
  "T cell",
  # note that we need to collapse the consensus to compare
  "myeloid",
  "fibroblast",
  "endothelial cell",
  "plasma cell",
  "natural killer cell", 
  "unknown_consensus" # we do want to keep these for this analysis
)

# Define the different myeloid types in consensus annotations
consensus_myeloid <- c("myeloid", "dendritic cell", "monocyte", "macrophage")


# Data frame with column `compare_annotations` that indicates
# if consensus and singler (roughly) match
compare_annotations_df <- celltype_recoded_df |>
  # recode labels for comparison
  dplyr::mutate(
    label_recoded = ifelse(
      label_recoded %in% consensus_myeloid,
      "myeloid",
      label_recoded
  )) |>
  dplyr::filter(label_recoded %in% direct_celltype_matches) |>
  dplyr::select(cell_id, annotation_type, label_recoded) |>
  # make it wider to label the situation
  tidyr::pivot_wider(
    names_from = "annotation_type",
    values_from = "label_recoded"
  ) |>
  # remove NAs:
  # - consensus NAs are those with other cell types not to include here
  # - singler NAs were unclassified cells
  tidyr::drop_na() |>
  dplyr::mutate(
    compare_annotations = dplyr::case_when(
      consensus_validation == "unknown_consensus" ~ "consensus not classified", 
      consensus_validation == singler ~ "matching annotations",
      consensus_validation != singler ~ "different annotations"
    ), 
    compare_annotations = forcats::fct_relevel(compare_annotations, "matching annotations", "different annotations", "consensus not classified")
  ) |>
  # we can remove the consensus annotation now
  dplyr::select(cell_id, singler, compare_annotations)


direct_markers_df <- consensus_markers_df |>
  dplyr::filter(validation_group_annotation %in% direct_celltype_matches) |>
  dplyr::select(marker_gene_label = validation_group_annotation, ensembl_gene_id, gene_symbol)
cells <- unique(compare_annotations_df$cell_id)
genes <- unique(direct_markers_df$ensembl_gene_id)
compare_annotations_expr_df <- scuttle::makePerCellDF(
  merged_sce[genes, cells],
  features = genes,
  assay.type = "logcounts",
  use.coldata = "cell_id",
  use.dimred = FALSE
) |>
  tidyr::pivot_longer(starts_with("ENSG"), names_to = "ensembl_gene_id", values_to = "logcounts") |>
  # filter to only cells with expression
  dplyr::filter(logcounts > 0) |>
  # join other data frames
  dplyr::left_join(compare_annotations_df, by = "cell_id") |>
  dplyr::left_join(direct_markers_df, by = "ensembl_gene_id", relationship = "many-to-many")
unique(compare_annotations_expr_df$singler) |>
  purrr::map(
    \(celltype) {
      
      plot_df <- compare_annotations_expr_df |>
        dplyr::filter(singler == celltype, marker_gene_label == celltype) 
      
      ggplot(plot_df) + 
        aes(x = gene_symbol, y = logcounts, fill = compare_annotations) +
        geom_boxplot(outlier.size = 0.25) + 
        ggtitle(glue::glue("cells annotated as: {celltype}"))
        
  }) |>
  patchwork::wrap_plots(ncol = 1) + patchwork::plot_layout(guides = "collect")

In most cases, gene expression for cells with matching consensus/SingleR annotations is higher or about the same as is gene expression for cells with different annotations.

Session Info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.5

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0 Biobase_2.64.0              GenomicRanges_1.56.2        GenomeInfoDb_1.40.1        
 [6] IRanges_2.38.1              S4Vectors_0.42.1            BiocGenerics_0.50.0         MatrixGenerics_1.16.0       matrixStats_1.5.0          
[11] patchwork_1.3.0             ggplot2_3.5.2              

loaded via a namespace (and not attached):
 [1] tidyselect_1.2.1          viridisLite_0.4.2         dplyr_1.1.4               farver_2.1.2              bitops_1.0-9              digest_0.6.37            
 [7] lifecycle_1.0.4           cluster_2.1.8             Cairo_1.6-2               magrittr_2.0.3            compiler_4.4.0            rlang_1.1.6              
[13] tools_4.4.0               yaml_2.3.10               knitr_1.50                labeling_0.4.3            S4Arrays_1.4.1            bit_4.6.0                
[19] curl_6.3.0                DelayedArray_0.30.1       plyr_1.8.9                RColorBrewer_1.1-3        abind_1.4-8               BiocParallel_1.38.0      
[25] withr_3.0.2               purrr_1.0.4               grid_4.4.0                beachmat_2.20.0           colorspace_2.1-1          scales_1.4.0             
[31] iterators_1.0.14          cli_3.6.5                 crayon_1.5.3              generics_0.1.4            httr_1.4.7                tzdb_0.5.0               
[37] rjson_0.2.23              DelayedMatrixStats_1.26.0 scuttle_1.14.0            stringr_1.5.1             zlibbioc_1.50.0           parallel_4.4.0           
[43] BiocManager_1.30.26       XVector_0.44.0            vctrs_0.6.5               Matrix_1.7-1              jsonlite_2.0.0            GetoptLong_1.0.5         
[49] hms_1.1.3                 bit64_4.6.0-1             clue_0.3-66               jpeg_0.1-11               foreach_1.5.2             tidyr_1.3.1              
[55] glue_1.8.0                codetools_0.2-20          stringi_1.8.7             shape_1.4.6.1             gtable_0.3.6              UCSC.utils_1.0.0         
[61] ComplexHeatmap_2.20.0     tibble_3.3.0              pillar_1.10.2             GenomeInfoDbData_1.2.12   circlize_0.4.16           R6_2.6.1                 
[67] sparseMatrixStats_1.16.0  doParallel_1.0.17         rprojroot_2.0.4           vroom_1.6.5               evaluate_1.0.3            lattice_0.22-6           
[73] readr_2.1.5               png_0.1-8                 renv_1.1.3                ggmap_4.0.1               Rcpp_1.0.14               SparseArray_1.4.8        
[79] xfun_0.52                 forcats_1.0.0             pkgconfig_2.0.3           GlobalOptions_0.1.2      
LS0tCnRpdGxlOiAiRXhwbG9yZSB0aGUgU2luZ2xlUiByZXN1bHRzIgphdXRob3I6ICJTdGVwaGFuaWUgSi4gU3BpZWxtYW4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhpcyBub3RlYm9va3MgZXhwbG9yZXMgdGhlIHJlc3VsdHMgZnJvbSBydW5uaW5nIGNlbGwgdHlwZSBhbm5vdGF0aW9uIHdpdGggYFNpbmdsZVJgIHVzaW5nIHRoZSBOQkF0bGFzLgpUaGUgTkJBdGxhcyByZWZlcmVuY2Ugd2FzIGFnZ3JlZ2F0ZWQgd2l0aCBgU2luZ2VsUmAgcHJpb3IgdG8gbW9kZWwgdHJhaW5pbmcuCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB2aXN1YWxpemUgaW5mZXJyZWQgY2VsbCB0eXBlIGFubm90YXRpb25zIGRpcmVjdGx5LCBjb21wYXJlIHRoZW0gdG8gbm9ybWFsIGNvbnNlbnN1cyBjZWxsIHR5cGVzLCBhbmQgdmFsaWRhdGUgY2VsbCB0eXBlIGFzc2lnbm1lbnRzIHdpdGggbWFya2VyIGdlbmVzLgoKCiMjIFNldHVwCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpvcHRpb25zKHJlYWRyLnNob3dfY29sX3R5cGVzID0gRkFMU0UpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCn0pCgp0aGVtZV9zZXQodGhlbWVfYncoKSkKCiMgRGVmaW5lIGNvbG9yIHJhbXAgZm9yIHNoYXJlZCB1c2UgaW4gdGhlIGhlYXRtYXBzCmhlYXRtYXBfY29sX2Z1biA8LSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKDAsIDEpLCBjb2xvcnMgPSBjKCJ3aGl0ZSIsICJkYXJrc2xhdGVibHVlIikpCiMgU2V0IGhlYXRtYXAgcGFkZGluZyBvcHRpb24KQ29tcGxleEhlYXRtYXA6Omh0X29wdChUSVRMRV9QQURESU5HID0gZ3JpZDo6dW5pdCgwLjYsICJpbiIpKQpgYGAKCgojIyMgUGF0aHMKCmBgYHtyIGJhc2UgcGF0aHN9CiMgVGhlIGJhc2UgcGF0aCBmb3IgdGhlIE9wZW5TY1BDQSByZXBvc2l0b3J5LCBmb3VuZCBieSBpdHMgKGhpZGRlbikgLmdpdCBkaXJlY3RvcnkKcmVwb3NpdG9yeV9iYXNlIDwtIHJwcm9qcm9vdDo6ZmluZF9yb290KHJwcm9qcm9vdDo6aXNfZ2l0X3Jvb3QpCgptb2R1bGVfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJjZWxsLXR5cGUtbmV1cm9ibGFzdG9tYS0wNCIpCnJlZl9kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZWZlcmVuY2VzIikKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZXN1bHRzIiwgInNpbmdsZXIiKQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IiwgIlNDUENQMDAwMDA0IikKYGBgCgoKYGBge3IgZmlsZSBwYXRoc30KIyBTaW5nbGVSIGZpbGVzCnNpbmdsZXJfZmlsZXMgPC0gbGlzdC5maWxlcygKICBwYXRoID0gcmVzdWx0c19kaXIsCiAgcGF0dGVybiA9ICJfc2luZ2xlci1hbm5vdGF0aW9uc1xcLnRzdiIsCiAgcmVjdXJzaXZlID0gVFJVRSwKICBmdWxsLm5hbWVzID0gVFJVRQopIHw+CiAgIyBhZGQgbmFtZXMgYXMgbGlicmFyeSBpZAogIHB1cnJyOjpzZXRfbmFtZXMoCiAgICBcKHgpIHsKICAgICAgc3RyaW5ncjo6c3RyX3JlbW92ZShiYXNlbmFtZSh4KSwgIl9zaW5nbGVyLWFubm90YXRpb25zLnRzdiIpCiAgICB9CiAgKQoKIyBtZXJnZWQgU0NFIGZpbGUKIyBPQlRBSU5FRCBGUk9NIFBPUlRBTCBESVJFQ1RMWSBvbiAyMDI1LTA3LTA5CnNjZV9maWxlIDwtIGZpbGUucGF0aCgKICBkYXRhX2RpciwKICAiU0NQQ1AwMDAwMDRfbWVyZ2VkLnJkcyIKKQoKIyBicm9hZCBjb25zZW5zdXMgY2VsbCB0eXBlIGdyb3Vwcwp2YWxpZGF0aW9uX3VybCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0FsZXhzTGVtb25hZGUvT3BlblNjUENBLWFuYWx5c2lzL3JlZnMvaGVhZHMvbWFpbi9hbmFseXNlcy9jZWxsLXR5cGUtY29uc2Vuc3VzL3JlZmVyZW5jZXMvY29uc2Vuc3VzLXZhbGlkYXRpb24tZ3JvdXBzLnRzdiIKCiMgcGFsZXR0ZSBmaWxlcwpyZWNvZGVkX3BhbGV0dGVfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbW9kdWxlX2RpciwKICAicGFsZXR0ZXMiLAogICJoYXJtb25pemVkLWNlbGwtdHlwZS1wYWxldHRlLnRzdiIKKQoKbmJhdGxhc19wYWxldHRlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1vZHVsZV9kaXIsCiAgInBhbGV0dGVzIiwKICAibmJhdGxhcy1tYXJrZXItZ2VuZXMtcGFsZXR0ZS50c3YiCikKCiMgbWFya2VyIGdlbmVzIGZvciB2YWxpZGF0aW9uCmNvbnNlbnN1c19tYXJrZXJzX2ZpbGUgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGV4c0xlbW9uYWRlL09wZW5TY1BDQS1hbmFseXNpcy9yZWZzL2hlYWRzL21haW4vYW5hbHlzZXMvY2VsbC10eXBlLWNvbnNlbnN1cy9yZWZlcmVuY2VzL3ZhbGlkYXRpb24tbWFya2Vycy50c3YiCm5iYXRsYXNfbWFya2Vyc19maWxlIDwtIGZpbGUucGF0aChyZWZfZGlyLCAibmJhdGxhcy1tYXJrZXItZ2VuZXMudHN2IikKYGBgCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KIyBTb3VyY2UgSmFjY2FyZCBhbmQgaGVhdG1hcCB1dGlsaXRpZXMgZnVuY3Rpb25zCnNvdXJjZShmaWxlLnBhdGgobW9kdWxlX2RpciwgInNjcmlwdHMiLCAidXRpbHMiLCAiamFjY2FyZC11dGlscy5SIikpCgojIFNvdXJjZSBhZGRpdGlvbmFsIHV0aWxpdGllcyBmdW5jdGlvbnM6CiMgLSBoYXJtb25pemVfY2VsbHR5cGVzKCkKIyAtIGZhY2V0ZWRfdW1hcCgpCiMgLSBnZW5lcmF0ZV9kb3RwbG90KCkKc291cmNlKGZpbGUucGF0aChtb2R1bGVfZGlyLCAic2NyaXB0cyIsICJ1dGlscyIsICJjZWxsdHlwZS11dGlscy5SIikpCmBgYAoKCgojIyMgUHJlcGFyZSBpbnB1dCBkYXRhCgpSZWFkIFNDRSBvYmplY3QgdG8gZ2V0IGNvbnNlbnN1cyBjZWxsIHR5cGVzIGFuZCBVTUFQIGNvb3JkaW5hdGVzOgoKYGBge3J9Cm1lcmdlZF9zY2UgPC0gcmVhZFJEUyhzY2VfZmlsZSkKIyBpbW1lZGlhdGVseSByZW1vdmUgYXNzYXlzIHdlIGRvbid0IG5lZWQgZm9yIHNwYWNlCmFzc2F5KG1lcmdlZF9zY2UsICJzcGxpY2VkIikgPC0gTlVMTAphc3NheShtZXJnZWRfc2NlLCAiY291bnRzIikgPC0gTlVMTAoKIyBSVUggUk9IISEhCiMgaHR0cHM6Ly9naXRodWIuY29tL0FsZXhzTGVtb25hZGUvc2NwY2FUb29scy9pc3N1ZXMvMjk4Cm1lcmdlZF9zY2UkY2VsbF9pZCA8LSBjb2xuYW1lcyhtZXJnZWRfc2NlKQpgYGAKClJlYWQgY2VsbCB0eXBlIGRhdGEgZnJhbWVzOgoKYGBge3J9CnNpbmdsZXJfZGYgPC0gc2luZ2xlcl9maWxlcyB8PgogIHB1cnJyOjptYXAocmVhZHI6OnJlYWRfdHN2KSB8PgogIHB1cnJyOjpsaXN0X3JiaW5kKG5hbWVzX3RvID0gImxpYnJhcnlfaWQiKSB8PgogIGRwbHlyOjpzZWxlY3QoLWRlbHRhLm5leHQsIC1sYWJlbHMpIHw+CiAgZHBseXI6Om11dGF0ZSgKICAgICMgYWRkIGNlbGwgaWQgY29sdW1uIGZvciB1bmlxdWUgcm93cyAmIGpvaW5pbmcKICAgIGNlbGxfaWQgPSBnbHVlOjpnbHVlKCJ7bGlicmFyeV9pZH0te2JhcmNvZGVzfSIpLAogICAgIyByZWNvZGUgTkEgLT4gInVua25vd25fc2luZ2xlciIKICAgIHBydW5lZC5sYWJlbHMgPSBpZmVsc2UoCiAgICAgIGlzLm5hKHBydW5lZC5sYWJlbHMpLAogICAgICAidW5rbm93bl9zaW5nbGVyIiwKICAgICAgcHJ1bmVkLmxhYmVscwogICAgKQogICkKCiMgdmFsaWRhdGlvbiBkYXRhIGZyYW1lcwp2YWxpZGF0aW9uX2RmIDwtIHJlYWRyOjpyZWFkX3Rzdih2YWxpZGF0aW9uX3VybCkgfD4KICBkcGx5cjo6c2VsZWN0KGNvbnNlbnN1c19hbm5vdGF0aW9uLCB2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24pCmNvbnNlbnN1c19tYXJrZXJzX2RmIDwtIHJlYWRyOjpyZWFkX3Rzdihjb25zZW5zdXNfbWFya2Vyc19maWxlKQpuYmF0bGFzX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfbWFya2Vyc19maWxlKQoKCiMgc2V0IHVwIHBhbGV0dGVzCgojIHJlY29kZWQgdG8gc2hhcmVkIGNvbG9ycwpyZWNvZGVkX3BhbGV0dGVfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHJlY29kZWRfcGFsZXR0ZV9maWxlKQpyZWNvZGVkX2NlbGx0eXBlX3BhbCA8LSByZWNvZGVkX3BhbGV0dGVfZGYkaGV4X2NvbG9yCm5hbWVzKHJlY29kZWRfY2VsbHR5cGVfcGFsKSA8LSByZWNvZGVkX3BhbGV0dGVfZGYkaGFybW9uaXplZF9sYWJlbAoKIyBvbmx5IHRoZSBOQkF0bGFzIGxhYmVscyAtIHVzZSBmb3IgdmFsaWRhdGlvbiBkb3RwbG90Cm5iYXRsYXNfcGFsZXR0ZV9kZiA8LSByZWFkcjo6cmVhZF90c3YobmJhdGxhc19wYWxldHRlX2ZpbGUpCm5iYXRsYXNfY2VsbHR5cGVfcGFsIDwtIG5iYXRsYXNfcGFsZXR0ZV9kZiRoZXhfY29sb3IKbmFtZXMobmJhdGxhc19jZWxsdHlwZV9wYWwpIDwtIG5iYXRsYXNfcGFsZXR0ZV9kZiROQkF0bGFzX2xhYmVsCgpgYGAKCkpvaW4gYW5kIHByZXBhcmUgZGF0YSBmb3IgdXNlOgoKYGBge3J9CmNlbGx0eXBlX2RmIDwtIHNjdXR0bGU6Om1ha2VQZXJDZWxsREYoCiAgbWVyZ2VkX3NjZSwKICB1c2UuY29sZGF0YSA9IGMoImJhcmNvZGVzIiwgInNhbXBsZV9pZCIsICJsaWJyYXJ5X2lkIiwgImNvbnNlbnN1c19jZWxsdHlwZV9hbm5vdGF0aW9uIiksCiAgdXNlLmRpbXJlZCA9IGMoIlVNQVAiKQopIHw+CiAgIyB0aGVyZSBhcmUgcmVwZWF0ZWQgYmFyY29kZXMgc28gd2UgbmVlZCB0byBrZWVwIGNlbGxfaWQgYXJvdW5kCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImNlbGxfaWQiKSB8PgogIGRwbHlyOjpyZW5hbWUoCiAgICBVTUFQMSA9IFVNQVAuMSwKICAgIFVNQVAyID0gVU1BUC4yLAogICAgY29uc2Vuc3VzX2Fubm90YXRpb24gPSBjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbgogICkgfD4KICBkcGx5cjo6bGVmdF9qb2luKHZhbGlkYXRpb25fZGYsIGJ5ID0gImNvbnNlbnN1c19hbm5vdGF0aW9uIikgfD4KICAjIHJlY29kZSBOQXMgdG8gInVua25vd25fY29uc2Vuc3VzIiBhbmQgcmVtb3ZlIGZ1bGwgY29uc2Vuc3VzIGxhYmVscwogIGRwbHlyOjptdXRhdGUodmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uID0gaWZlbHNlKAogICAgaXMubmEodmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uKSwKICAgICJ1bmtub3duX2NvbnNlbnN1cyIsCiAgICB2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24KICApKSB8PgogIGRwbHlyOjpzZWxlY3QoLWNvbnNlbnN1c19hbm5vdGF0aW9uKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2luZ2xlcl9kZiwgYnkgPSBjKCJjZWxsX2lkIiwgImJhcmNvZGVzIiwgImxpYnJhcnlfaWQiKSkKCiMgUmVjb2RlIE5CQXRsYXMgY2VsbCB0eXBlcyB3aGVyZSBwb3NzaWJsZSBzbyB0aGV5IG1hdGNoIHdpdGggY29sb3JzCmNlbGx0eXBlX3JlY29kZWRfZGYgPC0gY2VsbHR5cGVfZGYgfD4KICAjIHJlbmFtZSB0byBtYWtlIGFubm90YXRpb24gc291cmNlcyBtb3JlIGNsZWFyCiAgZHBseXI6OnJlbmFtZSgKICAgICJjb25zZW5zdXNfdmFsaWRhdGlvbiIgPSB2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24sCiAgICAic2luZ2xlciIgPSBwcnVuZWQubGFiZWxzCiAgKSB8PgogICMgcGl2b3QgbG9uZ2VyIGZvciB3cmFuZ2xpbmcKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgYyhjb25zZW5zdXNfdmFsaWRhdGlvbiwgc2luZ2xlciksCiAgICBuYW1lc190byA9ICJhbm5vdGF0aW9uX3R5cGUiLAogICAgdmFsdWVzX3RvID0gImxhYmVsIgogICkgfD4gCiAgaGFybW9uaXplX2NlbGx0eXBlcyhsYWJlbCwgbGFiZWxfcmVjb2RlZCkgfD4KICBkcGx5cjo6c2VsZWN0KC1sYWJlbCkKYGBgCgoKIyMgQ2VsbCB0eXBlcyBpbiBOQkF0bGFzCgpUaHJvdWdob3V0IHRoaXMgbm90ZWJvb2ssIHdlIGNvbXBhcmUgdGhlIGNlbGwgdHlwZSBsYWJlbHMgZnJvbSBgU2luZ2xlUmAgd2l0aCB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZSBsYWJlbHMuClRvIGZhY2lsaXRhdGUgdGhpcywgdGhlIHJlc3VsdGluZyBgU2luZ2xlUmAgbGFiZWxzIGZyb20gYE5CQXRsYXNgIHdlcmUgZ3JvdXBlZCB0b2dldGhlciB0byBib3RoIGEpIG1pcnJvciAod2hlcmUgcG9zc2libGUpIHRoZSBicm9hZCBjb25zZW5zdXMgbGFiZWxzLCBhbmQgYikgdG8gbWFrZSBzb21lIHZpc3VhbGl6YXRpb25zIG1vcmUgbWFuYWdlYWJsZS4KClRoZSB0YWJsZSBiZWxvdyBzaG93cyB0aGUgYE5CQXRsYXNgIGNlbGwgdHlwZXMgYW5kIGhvdyB0aGV5IGFyZSByZXByZXNlbnRlZCBpbiB2aXN1YWxpemF0aW9ucywgdW5sZXNzIG90aGVyd2lzZSBzdGF0ZWQuCgp8IEJyb2FkIGxhYmVsIHNob3duIGluIGZpZ3VyZXMgfCBBc3NvY2lhdGVkIE5CQXRsYXMgbGFiZWxzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IGBUIGNlbGxgICAgICAgICAgICAgICAgICAgICAgfCBgQ0Q0KyBUIGNlbGxgLCBgQ0Q4KyBUIGNlbGxgLCBgVHJlZ2AsIGBOS1QgY2VsbGAgICAgICAgICAgICAgICAgfAp8IGBkZW5kcml0aWMgY2VsbGAgICAgICAgICAgICAgfCBgY0RDMWAsIGBjREMyL0RDM2AsIGBNaWdyYXRvcnkgY0RDYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBuYXR1cmFsIGtpbGxlciBjZWxsIGNlbGxgICAgfCBgQ2lyY3VsYXRpbmcgTksgY2VsbGAsIGBUT1gyKy9LSVQrIE5LIGNlbGxgLCBgUmVzaWRlbnQgTksgY2VsbGAgfAp8IGBtb25vY3l0ZWAgICAgICAgICAgICAgICAgICAgfCBgQ0Q0KyBUIGNlbGxgLCBgQ2xhc3NpY2FsIG1vbm9jeXRlYCwgYFBhdHJvbGxpbmcgbW9ub2N5dGVgICAgICAgfAp8IGBteWVsb2lkYCAgICAgICAgICAgICAgICAgICAgfCBgTmV1dHJvcGhpbGAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBOZXVyb2VuZG9jcmluZWAgICAgICAgICAgICAgfCBgTmV1cm9lbmRvY3JpbmVgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBmaWJyb2JsYXN0YCAgICAgICAgICAgICAgICAgfCBgRmlicm9ibGFzdGAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBtYWNyb3BoYWdlYCAgICAgICAgICAgICAgICAgfCBgTWFjcm9waGFnZWAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBCIGNlbGxgICAgICAgICAgICAgICAgICAgICAgfCBgQiBjZWxsYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBwbGFzbWEgY2VsbGAgICAgICAgICAgICAgICAgfCBgUGxhc21hYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBlbmRvdGhlbGlhbGAgICAgICAgICAgICAgICAgfCBgRW5kb3RoZWxpYWxgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBTdHJvbWFsIG90aGVyIChOQkF0bGFzKWAgICAgfCBgU3Ryb21hbCBvdGhlcmAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBTY2h3YW5uYCAgICAgICAgICAgICAgICAgICAgfCBgU2Nod2FubmAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBSQkNzYCAgICAgICAgICAgICAgICAgICAgICAgfCBgUkJDc2AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBJbW11bmUgY3ljbGluZ2AgICAgICAgICAgICAgfCBgSW1tdW5lIGN5Y2xpbmdgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBwRENgICAgICAgICAgICAgICAgICAgICAgICAgfCBgcERDYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAoKQ2VsbHMgbGFiZWxlZCBgc2luZ2xlcl91bmtub3duYCBhcmUgdGhvc2UgZm9yIHdoaWNoIGBTaW5nbGVSYCBjb3VsZCBub3QgcmVsaWFibHkgYXNzaWduIGFuIGFubm90YXRpb24uCgojIyBIZWF0bWFwCgpUaGlzIHNlY3Rpb24gY29tcGFyZXMgU2luZ2xlUiBhbm5vdGF0aW9ucyB0byBjb25zZW5zdXMgY2VsbCB0eXBlIGFubm90YXRpb25zIHVzaW5nIGEgaGVhdG1hcC4KVGhlIGhlYXRtYXAgaXMgY29sb3JlZCBieSBKYWNjYXJkIHNpbWlsYXJpdHkgaW5kZXguCgpgYGB7ciwgZmlnLmhlaWdodCA9IDEwLCBmaWcud2lkdGggPSAxMH0gCiMgUGl2b3Qgd2lkZXIgZm9yIGhlYXRtYXAgZnVuY3Rpb25zCmNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX2RmIHw+CiAgdGlkeXI6OnBpdm90X3dpZGVyKAogICAgbmFtZXNfZnJvbSA9IGFubm90YXRpb25fdHlwZSwKICAgIHZhbHVlc19mcm9tID0gbGFiZWxfcmVjb2RlZAogICkKCmhlYXRtYXBfY29sX2Z1biA8LSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKDAsIDEpLCBjb2xvcnMgPSBjKCJ3aGl0ZSIsICJkYXJrc2xhdGVibHVlIikpCgptYWtlX2phY2NhcmRfaGVhdG1hcCgKICBjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYsCiAgImNvbnNlbnN1c192YWxpZGF0aW9uIiwKICAic2luZ2xlciIsCiAgIkNvbnNlbnN1cyB2YWxpZGF0aW9uIGxhYmVsIiwKICAiU2luZ2xlUiBOQkF0bGFzIGxhYmVsIgopCmBgYAoKV2UgYnJvYWRseSBzZWUgaGlnaCBjb21wYXRpYmlsaXR5IGJldHdlZW4gU2luZ2xlUiBhbmQgY29uc2Vuc3VzIGxhYmVscy4KSW4gYWRkaXRpb24sIFNpbmdsZVIgbW9zdGx5IGNsYXNzaWZpZXMgdGhlIHVua25vd24gY29uc2Vuc3VzIGNlbGxzIGFzIE5ldXJvZW5kb2NyaW5lLCB3aGljaCBpcyBjb25zaXN0ZW50IHdpdGggb3VyIGV4cGVjdGF0aW9uIHRoYXQgdGhlc2UgYXJlIG1vc3RseSB0dW1vciBjZWxscy4KCiMjIFVNQVBzCgpUaGlzIHNlY3Rpb24gdmlzdWFsaXplcyBhbmQgY29tcGFyZXMgU2luZ2xlUiBhbm5vdGF0aW9ucyB0byBjb25zZW5zdXMgY2VsbCB0eXBlIGFubm90YXRpb25zIHVzaW5nIFVNQVBzLgpOb3RlIHRoYXQgdGhlIGRpc3BsYXllZCBVTUFQIGlzIGZyb20gYSBtZXJnZWQgb2JqZWN0IHRoYXQgaGFzIF9ub3QgYmVlbiBiYXRjaC1jb3JyZWN0ZWQuXwoKQ2VsbCB0eXBlIGxhYmVscyBoYXZlIGJlZW4gaGFybW9uaXplZCBiZXR3ZWVuIHNvdXJjZXMgd2hlcmV2ZXIgcG9zc2libGUuCk5vdGUgdGhhdCBlYWNoIHNldCBvZiBsYWJlbHMgaGFzIGl0cyBvd24gInN0cm9tYWwiIGNhdGVnb3J5IHdoaWNoIHRoZSBsYWJlbHMgZGlzdGluZ3Vpc2guCkluIGFkZGl0aW9uLCBjZWxscyBsYWJlbGVkIGB1bmtub3duX2NvbnNlbnN1c2AgYXJlIHRob3NlIHdpdGggbm8gYXNzaWduZWQgY29uc2Vuc3VzIGxhYmVsLCBhbmQgY2VsbHMgbGFiZWxlZCBgdW5rbm93bl9zaW5nbGVyYCBhcmUgdGhvc2Ugd2hlcmUgU2luZ2xlUiBjb3VsZCBub3QgY29uZmlkZW50bHkgYXNzaWduIGEgbGFiZWwuCgojIyMgQ29tcGxldGUgVU1BUAoKRmlyc3QsIHdlIGRpc3BsYXkgdGhlIGNvbnNlbnN1cyBhbmQgU2luZ2xlUiBhbm5vdGF0aW9ucyBmb3IgYWxsIGNlbGxzLgoKYGBge3IsIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gN30KZ2dwbG90KGNlbGx0eXBlX3JlY29kZWRfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gbGFiZWxfcmVjb2RlZCkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuMjUsIGFscGhhID0gMC41KSArCiAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgdmFsdWVzID0gcmVjb2RlZF9jZWxsdHlwZV9wYWwsCiAgICBuYW1lID0gIkhhcm1vbml6ZWQgY2VsbCB0eXBlcyIKICApICsKICBmYWNldF93cmFwKHZhcnMoYW5ub3RhdGlvbl90eXBlKSkgKwogIGNvb3JkX2VxdWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKQpgYGAKCiMjIyBTaW5nbGVSIGFubm90YXRpb25zIG9ubHkKCkJlbG93IHdlIGRpc3BsYXkgYSBmYWNldGVkIFVNQVAgdG8gaGlnaGxpZ2h0IHRoZSBTaW5nbGVSIGFubm90YXRpb25zLgpMaWdodCBncmF5IGNlbGxzIGluIGVhY2ggcGFuZWwgcmVwcmVzZW50IGFsbCBvdGhlciBjZWxsIHR5cGVzLgoKYGBge3IgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSAxNn0KY2VsbHR5cGVfcmVjb2RlZF9kZiB8PgogIGRwbHlyOjpmaWx0ZXIoYW5ub3RhdGlvbl90eXBlID09ICJzaW5nbGVyIikgfD4KICBmYWNldGVkX3VtYXAoCiAgICBhbm5vdGF0aW9uX2NvbHVtbiA9IGxhYmVsX3JlY29kZWQsCiAgICBjZWxsdHlwZV9jb2xvcnMgPSByZWNvZGVkX2NlbGx0eXBlX3BhbAogICkKYGBgCgoKIyMjIEZhY2V0ZWQgY29tcGFyaXNvbiB0byBjb25zZW5zdXMgY2VsbCB0eXBlcwoKQmVsb3csIHdlIGRpc3BsYXkgZmFjZXRlZCBVTUFQcyBoaWdobGlnaHRpbmcgYSBzaW5nbGUgY2VsbCB0eXBlIGJ1dCBjb25zaWRlcmluZyBvbmx5IGNlbGwgdHlwZXMgdGhhdCBoYXZlIGRpcmVjdCBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIFNpbmdsZVIgYW5kIGNvbnNlbnN1cyBjZWxsIHR5cGVzLgpUaGlzIGFsbG93cyB1cyB0byBzZWUgaWYgdGhlIG5vcm1hbCBjZWxscyB0aGF0IFNpbmdsZVIgaXMgbGFiZWxpbmcgY29ycmVzcG9uZCB3ZWxsIHRvIHRoZSBub3JtYWwgY2VsbHMgaWRlbnRpZmllZCBieSBjb25zZW5zdXMgbGFiZWxzLgpFYWNoIHJvdyBkaXNwbGF5cyBhIGNlbGwgdHlwZSB3aGVyZSB0aGUgbGVmdCBjb2x1bW4gc2hvd3MgdGhlIGNvbnNlbnN1cyB2ZXJzaW9uIGFuZCB0aGUgcmlnaHQgY29sdW1uIHNob3dzIHRoZSBTaW5nbGVSIHZlcnNpb24uCgoKSW4gYWRkaXRpb24sIHdlIGluY2x1ZGUgYSBjYXRlZ29yeSBiZWxvdyBgcHV0YXRpdmUtdHVtb3JgIHRvIGRpcmVjdGx5IGNvbXBhcmUgdGhlIHVua25vd24gY29uc2Vuc3VzIGxhYmVscyB3aXRoIHRoZSBgTmV1cm9lbmRvY3JpbmVgIGNlbGxzIGxhYmVsZWQgYnkgU2luZ2xlUi4gCldoaWxlIHRoZXNlIGNhdGVnb3JpZXMgYXJlIG5vdCBuZWNlc3NhcmlseSBkaXJlY3RseSBjb21wYXJhYmxlLCB0aGV5IGFyZSBlYWNoIG1vc3QgbGlrZWx5IHRvIGNvbnRhaW4gdHVtb3IgY2VsbHMuCgpBbHNvLCBub3RlIHRoYXQgdGhlIGBteWVsb2lkYC1sYWJlbGVkIFNpbmdsZVIgY2VsbHMgYXJlIF9vbmx5XyBuZXV0cm9waGlscywgYnV0IHRoZSBjb25zZW5zdXMgbXllbG9pZCBncm91cGluZyBjb250YWlucyBhZGRpdGlvbmFsIGNlbGwgdHlwZXMuCgpgYGB7ciwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Mjh9CmRpcmVjdF9jZWxsdHlwZV9tYXRjaGVzIDwtIGMoCiAgIkIgY2VsbCIsCiAgIlQgY2VsbCIsCiAgIm15ZWxvaWQiLAogICJtYWNyb3BoYWdlIiwKICAibW9ub2N5dGUiLAogICJkZW5kcml0aWMgY2VsbCIsCiAgIm5hdHVyYWwga2lsbGVyIGNlbGwiLAogICJmaWJyb2JsYXN0IiwKICAiZW5kb3RoZWxpYWwgY2VsbCIsCiAgInBsYXNtYSBjZWxsIiwKICAibmF0dXJhbCBraWxsZXIgY2VsbCIsCiAgIyB3ZSdsbCB1c2UgdGhpcyBsYWJlbCB0byBiZSBhYmxlIHRvIGRpcmVjdGx5IGNvbXBhcmUgdW5rbm93bl9jb25zZW5zdXMgdG8gTmV1cm9lbmRvY3JpbmUKICAicHV0YXRpdmUtdHVtb3IiCikKCgpjZWxsdHlwZV9mYWNldF9kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX2RmIHw+CiAgZHBseXI6Om11dGF0ZSgKICAgICMgcmVjb2RlIHNvIE5ldXJvZW5kb2NyaW5lIG1hdGNoZXMgd2l0aCB1bmtub3duX2NvbnNlbnN1cyBpbiB0aGUgcGxvdAogICAgbGFiZWxfcmVjb2RlZCA9IGlmZWxzZSgKICAgICAgbGFiZWxfcmVjb2RlZCAlaW4lIGMoIk5ldXJvZW5kb2NyaW5lIiwgInVua25vd25fY29uc2Vuc3VzIiksCiAgICAgICJwdXRhdGl2ZS10dW1vciIsCiAgICAgIGxhYmVsX3JlY29kZWQKICAgICkpIHw+CiAgZHBseXI6OmZpbHRlcihsYWJlbF9yZWNvZGVkICVpbiUgZGlyZWN0X2NlbGx0eXBlX21hdGNoZXMpCgpmYWNldGVkX3VtYXAoCiAgY2VsbHR5cGVfZmFjZXRfZGYsCiAgYW5ub3RhdGlvbl9jb2x1bW4gPSBsYWJlbF9yZWNvZGVkLAogIGNlbGx0eXBlX2NvbG9ycyA9IHJlY29kZWRfY2VsbHR5cGVfcGFsLAogIGZhY2V0X3R5cGUgPSAiZ3JpZCIsCiAgYW5ub3RhdGlvbl90eXBlX2NvbHVtbiA9IGFubm90YXRpb25fdHlwZQopCmBgYAoKVGhlcmUgYXBwZWFycyB0byBiZSBhIHJlYXNvbmFibGUgY29ycmVzcG9uZGVuY2UgYmV0d2VlbiBjb25zZW5zdXMtIGFuZCBTaW5nbGVSLWNvbG9yZWQgVU1BUFMgZm9yIGVhY2ggY2VsbCB0eXBlLgpXZSBzZWUgdGhhdCBTaW5nbGVSIGNsYXNzaWZpZWQgbW9yZSBjZWxscyBjb21wYXJlZCB0byBjb25zZW5zdXMsIHdoaWNoIHdlIGV4cGVjdGVkLCBhbmQgd2UgYWxzbyBzZWUgdGhhdCB0aGUgYWRkaXRpb25hbCBjZWxscyB0aGF0IFNpbmdsZVIgbGFiZWxlZCBhcmUgcm91Z2hseSBpbiB0aGUgc2FtZSBuZWlnaGJvcmhvb2QgYXMgdGhlaXIgY29ycmVzcG9uZGluZyBjb25zZW5zdXMgbGFiZWxzLgoKCiMjIE1hcmtlciBnZW5lIGV4cHJlc3Npb24gZG90cGxvdHMKCkluIHRoaXMgc2VjdGlvbiB3ZSdsbCB2YWxpZGF0ZSBhbm5vdGF0aW9ucyB1c2luZyB0d28gc2V0cyBvZiBtYXJrZXIgZ2VuZXM6CgoqIE1hcmtlciBnZW5lcyBpZGVudGlmaWVkIGluIE5CQXRsYXMgY29ycmVzcG9uZGluZyB0byB0aGUgYXRsYXMgY2VsbCB0eXBlcwogICogVGhpcyB3aWxsIHRlbGwgdXMgaWYgd2UgYXJlIHBpY2tpbmcgdXAgY29tcGFyYWJsZSBzaWduYWwgaW4gb3VyIGRhdGEgdGhhdCBwbGF5cyB3ZWxsIHdpdGggTkJBdGxhcwoqIENvbnNlbnN1cyBjZWxsIHR5cGUgbWFya2VyIGdlbmVzCiAgKiBUaGlzIHdpbGwgcHJvdmlkZSBhbiBpbmRlcGVuZGVudCBhc3Nlc3NtZW50IG9mIHRoZSByZWxpYWJpbGl0eSBvZiBTaW5nbGVSIG5vcm1hbCBjZWxsIHR5cGUgYXNzaWdubWVudHMgCgpgYGB7cn0KIyBQcmVwYXJlIHRoZSBleHByZXNzZWRfZ2VuZXMgdmVjdG9yCiMgd2Ugb25seSBjYXJlIGFib3V0IGlmIHRoYXQgZ2VuZSBpcyBleHByZXNzZWQgb3RoZXJ3aXNlIHdlIHdvbid0IHdhc3RlIG1lbW9yeSBhbmQgaW5jbHVkZSBpdApnZW5lX3N1bXMgPC0gcm93RGF0YShtZXJnZWRfc2NlKSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIGRwbHlyOjpzZWxlY3QoY29udGFpbnMoImRldGVjdGVkIikpIHw+CiAgYXMubWF0cml4KCkgfD4KICByb3dTdW1zKCkKZXhwcmVzc2VkX2dlbmVzIDwtIG5hbWVzKGdlbmVfc3VtcylbZ2VuZV9zdW1zID4gMF0KYGBgCgoKCiMjIyBOQkF0bGFzIG1hcmtlciBnZW5lcwoKVGhpcyBkb3RwbG90IHNob3dzIHRoZSB0b3AtNSBoaWdoZXN0IGxvZzJGQyBtYXJrZXIgZ2VuZXMgcGVyIE5CQXRsYXMgY2VsbCB0eXBlIGZvciB2YWxpZGF0aW9uLgpNYXJrZXIgZ2VuZXMgYXJlIHRha2VuIGRpcmVjdGx5IGZyb20gdGhlIE5CQXRsYXMgcGFwZXIgd2hlcmUgdGhleSB3ZXJlIGlkZW50aWZpZWQgd2l0aCBgU2V1cmF0OjpGaW5kTWFya2VycygpYC4gCgpUaGlzIHBsb3QgZG9lcyBub3Qgc2hvdyB0aGUgcmVjb2RlZCBTaW5nbGVSIGxhYmVscyBidXQgcmF0aGVyIGFsbCBsYWJlbHMgdGhhdCBoYXZlIGFzc29jaWF0ZWQgbWFya2VyIGdlbmVzOyB0aGVyZWZvcmUsIHRoZXJlIGFyZSBtb3JlIGNlbGwgdHlwZSBjYXRlZ29yaWVzIGluIHRoaXMgcGxvdCB0aGFuIGluIG90aGVyIHBsb3RzIGluIHRoaXMgcmVwb3J0LgoKYGBge3J9CiMgbnVtYmVyIG9mIG1hcmtlciBnZW5lcyB0byBjb25zaWRlciBwZXIgdmFsaWRhdGlvbiBncm91cApuX21hcmtlcl9nZW5lcyA8LSA1Cgp0b3BfbmJhdGxhc19tYXJrZXJzX2RmIDwtIG5iYXRsYXNfbWFya2Vyc19kZiB8PgogICMga2VlcCBvbmx5IHRoZSB0b3AgYG5fbWFya2VyX2dlbmVzYAogIGRwbHlyOjpmaWx0ZXIoZGlyZWN0aW9uID09ICJ1cCIpIHw+CiAgZHBseXI6Omdyb3VwX2J5KE5CQXRsYXNfbGFiZWwpIHw+CiAgZHBseXI6Om11dGF0ZShyYW5rX2xmYyA9IHJhbmsoLWF2Z19sb2cyRkMpKSB8PgogIGRwbHlyOjp1bmdyb3VwKCkgfD4KICBkcGx5cjo6ZmlsdGVyKHJhbmtfbGZjIDw9IG5fbWFya2VyX2dlbmVzKSB8PgogIGRwbHlyOjpzZWxlY3QobWFya2VyX2dlbmVfbGFiZWwgPSBOQkF0bGFzX2xhYmVsLCBnZW5lX3N5bWJvbCwgZW5zZW1ibF9nZW5lX2lkKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gMjQsIGZpZy5oZWlnaHQgPSAxMn0KIyB3ZSB3YW50IHRvIHBsb3QgdGhlIGxpdGVyYWwgbGFiZWxzIGhlcmUKZnVsbF9jZWxsdHlwZV9kZiA8LSBjZWxsdHlwZV9kZiB8PgogIGRwbHlyOjpzZWxlY3QoCiAgICBjZWxsX2lkLCAKICAgICMgdGhlIGRvdHBsb3QgZnVuY3Rpb24gZXhwZWN0cyB0aGlzIGNvbHVtbiBuYW1lCiAgICBsYWJlbF9yZWNvZGVkID0gcHJ1bmVkLmxhYmVscwogICkgfD4KICBkcGx5cjo6bXV0YXRlKAogICAgbGFiZWxfcmVjb2RlZCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICMgY29sbGFwc2UgdGhlIGRlbmRyaXRpYyBjZWxscyAmIG1vbm9jeXRlcyB0byBtYXRjaCB3aGF0J3MgaW4gdGhlIG1hcmtlciBnZW5lcwogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGxhYmVsX3JlY29kZWQsICJjREMiKSB+ICJjREMiLCAKICAgICAgc3RyaW5ncjo6c3RyX2RldGVjdChsYWJlbF9yZWNvZGVkLCAibW9ub2N5dGUiKSB+ICJNb25vY3l0ZSIsCiAgICAgIGxhYmVsX3JlY29kZWQgPT0gIk5FIiB+ICJOZXVyb2VuZG9jcmluZSIsCiAgICAgIC5kZWZhdWx0ID0gbGFiZWxfcmVjb2RlZAogICkpIHw+CiAgZHBseXI6OmZpbHRlcihsYWJlbF9yZWNvZGVkICE9ICJ1bmtub3duX3NpbmdsZXIiKQoKCiMgZ2V0IHRvdGFsIG51bWJlciBvZiBjZWxscyBwZXIgZmluYWwgYW5ub3RhdGlvbiBncm91cCBhbmQgc2V0IHVwIHlfbGFiZWwKdG90YWxfY2VsbHNfZGYgPC0gZnVsbF9jZWxsdHlwZV9kZiB8PgogIGRwbHlyOjpjb3VudChsYWJlbF9yZWNvZGVkLCBuYW1lID0gInRvdGFsX2NlbGxzIikgfD4KICBkcGx5cjo6YXJyYW5nZShkZXNjKHRvdGFsX2NlbGxzKSkgfD4KICBkcGx5cjo6bXV0YXRlKHlfbGFiZWwgPSBnbHVlOjpnbHVlKCJ7bGFiZWxfcmVjb2RlZH0gKHt0b3RhbF9jZWxsc30pIikpCgpzaW5nbGVyX29yZGVyIDwtIHRvdGFsX2NlbGxzX2RmJHlfbGFiZWwKdG90YWxfY2VsbHNfZGYkeV9sYWJlbCA8LSBmYWN0b3IodG90YWxfY2VsbHNfZGYkeV9sYWJlbCwgbGV2ZWxzID0gc2luZ2xlcl9vcmRlcikKbmJhdGxhc19iYXJfb3JkZXIgPC0gdG90YWxfY2VsbHNfZGYkbGFiZWxfcmVjb2RlZApgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gMzIsIGZpZy5oZWlnaHQgPSAxNn0KZ2VuZXJhdGVfZG90cGxvdCgKICBtZXJnZWRfc2NlID0gbWVyZ2VkX3NjZSwKICBtYXJrZXJzX2RmID0gdG9wX25iYXRsYXNfbWFya2Vyc19kZiwKICBzaW5nbGVyX2RmID0gZnVsbF9jZWxsdHlwZV9kZiwKICB0b3RhbF9jZWxsc19kZiA9IHRvdGFsX2NlbGxzX2RmLAogIGV4cHJlc3NlZF9nZW5lcyA9IGV4cHJlc3NlZF9nZW5lcywKICBiYXJfb3JkZXIgPSBuYmF0bGFzX2Jhcl9vcmRlciwgCiAgY2VsbHR5cGVfcGFsZXR0ZSA9IG5iYXRsYXNfY2VsbHR5cGVfcGFsLCAKICBtaW5fY2VsbHMgPSAwCikKYGBgCkZvciB2YWxpZGF0aW9uLCB3ZSBleHBlY3QgdG8gc2VlIGhpZ2ggbWFya2VyIGdlbmUgZXhwcmVzc2lvbiBhbG9uZyB0aGUgZGlhZ29uYWwgb2YgdGhlIGRvdHBsb3Qgd2hlcmUgY2VsbCB0eXBlIGxhYmVscyBtYXRjaCB3aXRoIHRoZWlyIG1hcmtlciBnZW5lcy4KV2UgZG8gYnJvYWRseSBzZWUgaGlnaCBleHByZXNzaW9uIGFsb25nIHRoZSBkaWFnb25hbCwgd2hpY2ggbWVhbnMgb3VyIGxhYmVsZWQgY2VsbHMgaGF2ZSBzb21lIGFtb3VudCBvZiBzaW1pbGFyIHNpZ25hbCB0byB0aG9zZSBpbiBOQkF0bGFzLgpTb21lIHNldHMgb2YgbWFya2VyIGdlbmVzIHNob3cgaGlnaCBleHByZXNzaW9uIGFjcm9zcyBtdWx0aXBsZSBjZWxsIHR5cGUgY2F0ZWdvcmllcyBpbiBwYXJ0aWN1bGFyIGZvciBjbG9zZWx5IHJlbGF0ZWQgY2VsbCB0eXBlcyAoZS5nLiBtb25vY3l0ZSwgbWFjcm9waGFnZSwgbXllbG9pZCwgZGVuZHJpdGljLCBvciBULWNlbGxzIGFuZCBOSy1jZWxscyksIGJ1dCB0aGVyZSBkbyBub3QgYXBwZWFyIHRvIGJlIGFueSBzdHJvbmdseSB1bmV4cGVjdGVkIGdlbmUgZXhwcmVzc2lvbiBwYXR0ZXJucy4KCgoKIyMjIENvbnNlbnN1cyB2YWxpZGF0aW9uIG1hcmtlciBnZW5lcwoKVGhpcyBkb3RwbG90IHNob3dzIGV4cHJlc3Npb24gb2YgY29uc2Vuc3VzIHZhbGlkYXRpb24gbWFya2VyIGdlbmVzIGFjcm9zcyBtYXRjaGluZyBjZWxsIHR5cGVzIGxhYmVsZWQgd2l0aCBTaW5nbGVSLgoKYGBge3J9CiMgUHJlcGFyZSBkYXRhIGZyYW1lIHdpdGggc2luZ2xlciBsYWJlbHMgdG8gcGxvdApzaW5nbGVyX3JlY29kZWRfZGYgPC0gY2VsbHR5cGVfcmVjb2RlZF93aWRlX2RmIHw+CiAgIyB3ZSBkb24ndCBjb25zaWRlciB0aGUgTkFzIGhlcmUKICBkcGx5cjo6ZmlsdGVyKHNpbmdsZXIgIT0gInVua25vd25fc2luZ2xlciIpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBsYWJlbF9yZWNvZGVkID0gc2luZ2xlcikKCgojIGdldCB0b3RhbCBudW1iZXIgb2YgY2VsbHMgcGVyIGZpbmFsIGFubm90YXRpb24gZ3JvdXAgYW5kIHNldCB1cCB5X2xhYmVsCnRvdGFsX2NlbGxzX2RmIDwtIHNpbmdsZXJfcmVjb2RlZF9kZiB8PgogIGRwbHlyOjpjb3VudChsYWJlbF9yZWNvZGVkLCBuYW1lID0gInRvdGFsX2NlbGxzIikgfD4KICBkcGx5cjo6YXJyYW5nZShkZXNjKHRvdGFsX2NlbGxzKSkgfD4KICBkcGx5cjo6bXV0YXRlKHlfbGFiZWwgPSBnbHVlOjpnbHVlKCJ7bGFiZWxfcmVjb2RlZH0gKHt0b3RhbF9jZWxsc30pIikpCgpzaW5nbGVyX29yZGVyIDwtIHRvdGFsX2NlbGxzX2RmJHlfbGFiZWwKdG90YWxfY2VsbHNfZGYkeV9sYWJlbCA8LSBmYWN0b3IodG90YWxfY2VsbHNfZGYkeV9sYWJlbCwgbGV2ZWxzID0gc2luZ2xlcl9vcmRlcikKYGBgCgoKCmBgYHtyfQojIHByZXBhcmUgbWFya2VycyBmb3IgZG90cGxvdApkb3RwbG90X2NvbnNlbnN1c19tYXJrZXJzX2RmIDwtIGNvbnNlbnN1c19tYXJrZXJzX2RmIHw+CiAgIyBjb25zaWRlciBvbmx5IG1hdGNoaW5nIGxhYmVscwogIGRwbHlyOjpmaWx0ZXIodmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uICVpbiUgc2luZ2xlcl9yZWNvZGVkX2RmJGxhYmVsX3JlY29kZWQpIHw+CiAgZHBseXI6OnNlbGVjdCgKICAgIG1hcmtlcl9nZW5lX2xhYmVsID0gdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uLAogICAgZW5zZW1ibF9nZW5lX2lkLAogICAgZ2VuZV9zeW1ib2wKICApCgojIGdldCB0aGUgYmFyIG9yZGVyCmNvbnNlbnN1c19iYXJfb3JkZXIgPC0gdG90YWxfY2VsbHNfZGYgfD4KICBkcGx5cjo6c2VsZWN0KGxhYmVsX3JlY29kZWQsIHlfbGFiZWwpIHw+CiAgIyBpbm5lciEhISEKICBkcGx5cjo6aW5uZXJfam9pbigKICAgIGRvdHBsb3RfY29uc2Vuc3VzX21hcmtlcnNfZGYsIAogICAgYnkgPSBjKCJsYWJlbF9yZWNvZGVkIiA9ICJtYXJrZXJfZ2VuZV9sYWJlbCIpCiAgKSB8PgogIGRwbHlyOjphcnJhbmdlKHlfbGFiZWwpIHw+CiAgZHBseXI6OnB1bGwobGFiZWxfcmVjb2RlZCkgfD4KICB1bmlxdWUoKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gMjYsIGZpZy5oZWlnaHQgPSAxMn0KZ2VuZXJhdGVfZG90cGxvdCgKICBtZXJnZWRfc2NlID0gbWVyZ2VkX3NjZSwKICBtYXJrZXJzX2RmID0gZG90cGxvdF9jb25zZW5zdXNfbWFya2Vyc19kZiwKICBzaW5nbGVyX2RmID0gc2luZ2xlcl9yZWNvZGVkX2RmLAogIHRvdGFsX2NlbGxzX2RmID0gdG90YWxfY2VsbHNfZGYsCiAgZXhwcmVzc2VkX2dlbmVzID0gZXhwcmVzc2VkX2dlbmVzLAogIGJhcl9vcmRlciA9IGNvbnNlbnN1c19iYXJfb3JkZXIsIAogIGNlbGx0eXBlX3BhbGV0dGUgPSByZWNvZGVkX2NlbGx0eXBlX3BhbCwgCiAgbWluX2NlbGxzID0gMAopCmBgYAoKQWdhaW4sIG1hcmtlciBnZW5lIGV4cHJlc3Npb24gdGVuZHMgdG8gYmUgaGlnaCBpbiB0aGUgbWF0Y2hpbmcgY2VsbCB0eXBlIGNhdGVnb3JpZXMsIHdpdGggdGhlIHBvc3NpYmxlIGV4Y2VwdGlvbiBvZiBgQiBjZWxsYCB3aGljaCBoYXMgc29tZXdoYXQgbG93ZXIgZ2VuZSBleHByZXNzaW9uIG9mIGZld2VyIGdlbmVzOyBidXQsIHRoZXJlIGlzIHN0aWxsIHNvbWUgZXhwZWN0ZWQgZXhwcmVzc2lvbiB0aGVyZS4KV2UgYWxzbyBzZWUgc29tZSAicHJvbWlzY3VpdHkiIHdoZXJlIG1hcmtlciBnZW5lcyBmb3IgY2xvc2VseS1yZWxhdGVkIGNlbGwgdHlwZXMgc2hvdyBoaWdoIGV4cHJlc3Npb24uIAoKCiMjIENvbXBhcmUgZXhwcmVzc2lvbiB3aGVuIFNpbmdsZVIgYW5kIGNvbnNlbnN1cyBhbm5vdGF0aW9ucyBtYXRjaAoKTmV4dCwgd2UgbG9vayBhdCBleHByZXNzaW9uIG9mIGNvbnNlbnN1cyB2YWxpZGF0aW9uIG1hcmtlciBnZW5lcyBmb3IgZWFjaCBub3JtYWwvaGFybW9uaXplZCBjZWxsIHR5cGUgbGFiZWwuCldlIGNvbXBhcmUgZXhwcmVzc2lvbiBhY3Jvc3MgZ3JvdXBzIG9mIGNlbGxzOgoKKiBgbWF0Y2hpbmcgYW5ub3RhdGlvbnNgOiBDZWxscyB3aG9zZSBjb25zZW5zdXMgYW5ub3RhdGlvbiBtYXRjaGVzIHRoZSBTaW5nbGVSIGFubm90YXRpb24KKiBgZGlmZmVyZW50IGFubm90YXRpb25zYDogQ2VsbHMgd2hvc2UgY29uc2Vuc3VzIGFubm90YXRpb24gZGlmZmVycyBmcm9tIHRoZSBTaW5nbGVSIGFubm90YXRpb24KKiBgY29uc2Vuc3VzIG5vdCBjbGFzc2lmaWVkYDogQ2VsbHMgd2hlcmUgdGhlIGNvbnNlbnN1cyBhbm5vdGF0aW9uIGlzIHVua25vd24KCkVhY2ggcGFuZWwgY29udGFpbnMgb25seSBjZWxscyB0aGF0IFNpbmdsZVIgYW5ub3RhdGVkIHdpdGggdGhlIGdpdmVuIGxhYmVsLCBhbmQgc2hvd3Mgb25seSBtYXJrZXIgZ2VuZXMgYXNzb2NpYXRlZCB3aXRoIHRoYXQgZ2l2ZW4gY2VsbCB0eXBlLiAKV2UgZXhwZWN0IHRoYXQgY2VsbHMgd2hvc2UgY29uc2Vuc3VzIGFuZCBTaW5nbGVSIGFubm90YXRpb25zIGFncmVlIG1heSBzaG93IGhpZ2hlciBnZW5lIGV4cHJlc3Npb24gZm9yIHRoZSBnaXZlbiBtYXJrZXIuCgpgYGB7cn0KIyB0aGlzIHRpbWUgd2UgYXJlIG5vdCBjb25zaWRlcmluZyBwdXRhdGl2ZSB0dW1vcgpkaXJlY3RfY2VsbHR5cGVfbWF0Y2hlcyA8LSBjKAogICJCIGNlbGwiLAogICJUIGNlbGwiLAogICMgbm90ZSB0aGF0IHdlIG5lZWQgdG8gY29sbGFwc2UgdGhlIGNvbnNlbnN1cyB0byBjb21wYXJlCiAgIm15ZWxvaWQiLAogICJmaWJyb2JsYXN0IiwKICAiZW5kb3RoZWxpYWwgY2VsbCIsCiAgInBsYXNtYSBjZWxsIiwKICAibmF0dXJhbCBraWxsZXIgY2VsbCIsIAogICJ1bmtub3duX2NvbnNlbnN1cyIgIyB3ZSBkbyB3YW50IHRvIGtlZXAgdGhlc2UgZm9yIHRoaXMgYW5hbHlzaXMKKQoKIyBEZWZpbmUgdGhlIGRpZmZlcmVudCBteWVsb2lkIHR5cGVzIGluIGNvbnNlbnN1cyBhbm5vdGF0aW9ucwpjb25zZW5zdXNfbXllbG9pZCA8LSBjKCJteWVsb2lkIiwgImRlbmRyaXRpYyBjZWxsIiwgIm1vbm9jeXRlIiwgIm1hY3JvcGhhZ2UiKQoKCiMgRGF0YSBmcmFtZSB3aXRoIGNvbHVtbiBgY29tcGFyZV9hbm5vdGF0aW9uc2AgdGhhdCBpbmRpY2F0ZXMKIyBpZiBjb25zZW5zdXMgYW5kIHNpbmdsZXIgKHJvdWdobHkpIG1hdGNoCmNvbXBhcmVfYW5ub3RhdGlvbnNfZGYgPC0gY2VsbHR5cGVfcmVjb2RlZF9kZiB8PgogICMgcmVjb2RlIGxhYmVscyBmb3IgY29tcGFyaXNvbgogIGRwbHlyOjptdXRhdGUoCiAgICBsYWJlbF9yZWNvZGVkID0gaWZlbHNlKAogICAgICBsYWJlbF9yZWNvZGVkICVpbiUgY29uc2Vuc3VzX215ZWxvaWQsCiAgICAgICJteWVsb2lkIiwKICAgICAgbGFiZWxfcmVjb2RlZAogICkpIHw+CiAgZHBseXI6OmZpbHRlcihsYWJlbF9yZWNvZGVkICVpbiUgZGlyZWN0X2NlbGx0eXBlX21hdGNoZXMpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBhbm5vdGF0aW9uX3R5cGUsIGxhYmVsX3JlY29kZWQpIHw+CiAgIyBtYWtlIGl0IHdpZGVyIHRvIGxhYmVsIHRoZSBzaXR1YXRpb24KICB0aWR5cjo6cGl2b3Rfd2lkZXIoCiAgICBuYW1lc19mcm9tID0gImFubm90YXRpb25fdHlwZSIsCiAgICB2YWx1ZXNfZnJvbSA9ICJsYWJlbF9yZWNvZGVkIgogICkgfD4KICAjIHJlbW92ZSBOQXM6CiAgIyAtIGNvbnNlbnN1cyBOQXMgYXJlIHRob3NlIHdpdGggb3RoZXIgY2VsbCB0eXBlcyBub3QgdG8gaW5jbHVkZSBoZXJlCiAgIyAtIHNpbmdsZXIgTkFzIHdlcmUgdW5jbGFzc2lmaWVkIGNlbGxzCiAgdGlkeXI6OmRyb3BfbmEoKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICBjb21wYXJlX2Fubm90YXRpb25zID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgY29uc2Vuc3VzX3ZhbGlkYXRpb24gPT0gInVua25vd25fY29uc2Vuc3VzIiB+ICJjb25zZW5zdXMgbm90IGNsYXNzaWZpZWQiLCAKICAgICAgY29uc2Vuc3VzX3ZhbGlkYXRpb24gPT0gc2luZ2xlciB+ICJtYXRjaGluZyBhbm5vdGF0aW9ucyIsCiAgICAgIGNvbnNlbnN1c192YWxpZGF0aW9uICE9IHNpbmdsZXIgfiAiZGlmZmVyZW50IGFubm90YXRpb25zIgogICAgKSwgCiAgICBjb21wYXJlX2Fubm90YXRpb25zID0gZm9yY2F0czo6ZmN0X3JlbGV2ZWwoY29tcGFyZV9hbm5vdGF0aW9ucywgIm1hdGNoaW5nIGFubm90YXRpb25zIiwgImRpZmZlcmVudCBhbm5vdGF0aW9ucyIsICJjb25zZW5zdXMgbm90IGNsYXNzaWZpZWQiKQogICkgfD4KICAjIHdlIGNhbiByZW1vdmUgdGhlIGNvbnNlbnN1cyBhbm5vdGF0aW9uIG5vdwogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgc2luZ2xlciwgY29tcGFyZV9hbm5vdGF0aW9ucykKCgpkaXJlY3RfbWFya2Vyc19kZiA8LSBjb25zZW5zdXNfbWFya2Vyc19kZiB8PgogIGRwbHlyOjpmaWx0ZXIodmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uICVpbiUgZGlyZWN0X2NlbGx0eXBlX21hdGNoZXMpIHw+CiAgZHBseXI6OnNlbGVjdChtYXJrZXJfZ2VuZV9sYWJlbCA9IHZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiwgZW5zZW1ibF9nZW5lX2lkLCBnZW5lX3N5bWJvbCkKY2VsbHMgPC0gdW5pcXVlKGNvbXBhcmVfYW5ub3RhdGlvbnNfZGYkY2VsbF9pZCkKZ2VuZXMgPC0gdW5pcXVlKGRpcmVjdF9tYXJrZXJzX2RmJGVuc2VtYmxfZ2VuZV9pZCkKYGBgCgoKYGBge3J9CmNvbXBhcmVfYW5ub3RhdGlvbnNfZXhwcl9kZiA8LSBzY3V0dGxlOjptYWtlUGVyQ2VsbERGKAogIG1lcmdlZF9zY2VbZ2VuZXMsIGNlbGxzXSwKICBmZWF0dXJlcyA9IGdlbmVzLAogIGFzc2F5LnR5cGUgPSAibG9nY291bnRzIiwKICB1c2UuY29sZGF0YSA9ICJjZWxsX2lkIiwKICB1c2UuZGltcmVkID0gRkFMU0UKKSB8PgogIHRpZHlyOjpwaXZvdF9sb25nZXIoc3RhcnRzX3dpdGgoIkVOU0ciKSwgbmFtZXNfdG8gPSAiZW5zZW1ibF9nZW5lX2lkIiwgdmFsdWVzX3RvID0gImxvZ2NvdW50cyIpIHw+CiAgIyBmaWx0ZXIgdG8gb25seSBjZWxscyB3aXRoIGV4cHJlc3Npb24KICBkcGx5cjo6ZmlsdGVyKGxvZ2NvdW50cyA+IDApIHw+CiAgIyBqb2luIG90aGVyIGRhdGEgZnJhbWVzCiAgZHBseXI6OmxlZnRfam9pbihjb21wYXJlX2Fubm90YXRpb25zX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKGRpcmVjdF9tYXJrZXJzX2RmLCBieSA9ICJlbnNlbWJsX2dlbmVfaWQiLCByZWxhdGlvbnNoaXAgPSAibWFueS10by1tYW55IikKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMjB9CnVuaXF1ZShjb21wYXJlX2Fubm90YXRpb25zX2V4cHJfZGYkc2luZ2xlcikgfD4KICBwdXJycjo6bWFwKAogICAgXChjZWxsdHlwZSkgewogICAgICAKICAgICAgcGxvdF9kZiA8LSBjb21wYXJlX2Fubm90YXRpb25zX2V4cHJfZGYgfD4KICAgICAgICBkcGx5cjo6ZmlsdGVyKHNpbmdsZXIgPT0gY2VsbHR5cGUsIG1hcmtlcl9nZW5lX2xhYmVsID09IGNlbGx0eXBlKSAKICAgICAgCiAgICAgIGdncGxvdChwbG90X2RmKSArIAogICAgICAgIGFlcyh4ID0gZ2VuZV9zeW1ib2wsIHkgPSBsb2djb3VudHMsIGZpbGwgPSBjb21wYXJlX2Fubm90YXRpb25zKSArCiAgICAgICAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDAuMjUpICsgCiAgICAgICAgZ2d0aXRsZShnbHVlOjpnbHVlKCJjZWxscyBhbm5vdGF0ZWQgYXM6IHtjZWxsdHlwZX0iKSkKICAgICAgICAKICB9KSB8PgogIHBhdGNod29yazo6d3JhcF9wbG90cyhuY29sID0gMSkgKyBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKYGBgCgpJbiBtb3N0IGNhc2VzLCBnZW5lIGV4cHJlc3Npb24gZm9yIGNlbGxzIHdpdGggbWF0Y2hpbmcgY29uc2Vuc3VzL1NpbmdsZVIgYW5ub3RhdGlvbnMgaXMgaGlnaGVyIG9yIGFib3V0IHRoZSBzYW1lIGFzIGlzIGdlbmUgZXhwcmVzc2lvbiBmb3IgY2VsbHMgd2l0aCBkaWZmZXJlbnQgYW5ub3RhdGlvbnMuCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyIHNlc3Npb24gaW5mb30KIyByZWNvcmQgdGhlIHZlcnNpb25zIG9mIHRoZSBwYWNrYWdlcyB1c2VkIGluIHRoaXMgYW5hbHlzaXMgYW5kIG90aGVyIGVudmlyb25tZW50IGluZm9ybWF0aW9uCnNlc3Npb25JbmZvKCkKYGBgCg==